'''EditorNotebookPlugin

Provides the editor notebook panel (rwb.core.editor_notebook) which is used
to manage open test suites and files
'''

from __future__ import with_statement

# decorator which can be added to a method
def timeit(func):
    import time
    def wrapper(*arg):
        t1 = time.time()
        res = func(*arg)
        t2 = time.time()
        print '%s took %0.3f ms' % (func.func_name, (t2-t1)*1000.0)
        return res
    return wrapper

from contextlib import contextmanager
import hashlib
import re
import wx
import wx.lib.agw.aui as aui
import rwb
import sys
import firebrick
import wx.stc
from dtc import DynamicTableCtrl, EVT_CELL_FOCUS_CHANGED, STYLE_COMMENT
from robot.parsing import TestDataDirectory, TestCaseFile, TestCase
from robot.parsing.settings import Fixture, Template, Timeout
from robot.parsing.model import Step
from rwb.CodeEditor import CodeEditor
from dtc.DynamicTableCtrl import STYLE_SEP
#from rwb.AwesomeBox import AwesomeBox
import os.path
from rwb.RobotFormatter import TsvFormatter
from rwb.util import get_longname
import wx.lib.agw.hyperlink

# eventually, modes like this will be registerable. For now these
# are the only modes I've got.
from rwb.PythonMode import PythonMode
from rwb.widgets import Breadcrumbs

FID = "rwb.core.edit_notebook"
STYLE_VARIABLE = 10
STYLE_KEYWORD = 11
STYLE_SETTING = 12

class EditorNotebookPlugin(firebrick.Plugin):
    def __init__(self, app):
        super(EditorNotebookPlugin, self).__init__(_id=FID,
                            required = True,
                            url=None,
                            version="0.1",
                            name = "Editor Notebook",
                            default_settings = {})
        
    def enable(self):
        self.app.Register(EditorNotebook)
        self.app.Register(TestCaseEditor)
        self.app.Register(TestCaseFileEditor)
        self.app.Register(TestDataDirectoryEditor)
        self.app.Register(TextEditor)

        self.active = True

    def disable(self):
        self.app.Unregister(EditorNotebook)
        pass

class EditorNotebook(firebrick.BasePanel):
    '''View for editing data'''

    name = "Editor"
    _id = FID

    def __init__(self, window):
        super(EditorNotebook, self).__init__(window)
        self._notebook = aui.AuiNotebook(self, wx.ID_ANY)
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self._notebook, 1, wx.EXPAND)
        self.SetSizerAndFit(sizer)

        window.auimgr.AddPane(self, aui.AuiPaneInfo().
                              Center().
                              CaptionVisible(True).
                              Caption(self.name).
                              Floatable(False).
                              Movable(False).
                              Show(False).
                              Name(FID).
                              BestSize(wx.Size(600,200)))
        self.app.Subscribe("object_activated", self.OnObjectActivated)

    def _find_page(self, obj):
        '''Return the index for the page that contains obj, or None

        At some future point in time it might be possible to have the
        same obj open in two tabs (say, a grid editor and text editor.
        For now this method returns the first tab it finds. Maybe
        something smarter will be necessary later?
        '''
        for i in range(self._notebook.GetPageCount()):
            page = self._notebook.GetPage(i)
            if page.obj == obj:
                return i
        return None

    def GetCurrentEditor(self):
        '''Return the editor associated with the currently selected tab'''
        page_index = self._notebook.GetSelection()
        if page_index >= 0:
            page = self._notebook.GetPage(page_index)
            return page
        return None


    def Open(self, obj, select=True, target=None):
        '''Open an object in the editor

        If there is already a tab open for this object, that
        tab will be selected (ie: it won't be opened twice)
        '''
        # is the editor panel visible? If not ,
        # make it visible. Otherwise, do nothing
        rwb.log.debug("Open(%s) in editor" % obj)
        self.window.ShowPanels(self._id)
        self.window.ShowToolbars("rwb.toolbar.edit")
            
        page_index = self._find_page(obj)
        if page_index is not None and select:
            # object is already open in an editor tab
            editor = self._notebook.GetPage(page_index)
            self._notebook.SetSelectionToWindow(editor)

        elif page_index is None:
            # object is not open; create a new tab
            editor = self.EditorFactory(obj)
            self._notebook.AddPage(editor, editor.title, select=select)

        if target is not None:
            editor.HighlightTarget(target)

    def Save(self):
        print "Save!"
        page_index = self._notebook.GetSelection()
        page = self._notebook.GetPage(page_index)
        print "... page=", page
        page.Save()

    def OnObjectActivated(self, message):
        '''Called when a user double-clicks an item in the navigator'''
        obj = message.data["object"]
        self.Open(obj, select=True)


    def EditorFactory(self, obj):
        '''Return an appropriate editor for the given object

        Each type of object has a default editor, but "editor"
        can define a specific editor (so one could choose to
        open something in the text editor instead of the grid,
        for example)
        '''
        if isinstance(obj, TestCase):
            edit_ctrl = TestCaseEditor(self._notebook, obj)
        elif isinstance(obj, TestCaseFile):
            edit_ctrl = TestCaseFileEditor(self._notebook, obj)
        elif isinstance(obj, TestDataDirectory):
            edit_ctrl = TestDataDirectoryEditor(self._notebook, obj)
        else:
            edit_ctrl = TextEditor(self._notebook, obj)
        return edit_ctrl
        
class Header(wx.Panel):
    def __init__(self, parent, editor):
        super(Header,self).__init__(parent, wx.ID_ANY)
        self._change_events_enabled = True
        self.editor = editor
        self.obj = None
        # this color should probably be gotten through introspection...
        self.breadcrumbs = Breadcrumbs(self, size=(50,50)) # need to compute height based on font
        self.SetBackgroundColour("#ffffff")
        self.breadcrumbs.SetBackgroundColour("#ffffff")
#        self.breadcrumbs.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND))
        self.doc = wx.TextCtrl(self, wx.ID_ANY, size=(-1, 10), style=wx.TE_MULTILINE|wx.NO_BORDER)
        self.doc.SetBackgroundColour("#EBF3FF")
        self.docLabel = wx.StaticText(self, wx.ID_ANY, "Description:")
        sizer = wx.GridBagSizer(hgap=5, vgap=5)
        sizer.Add(self.breadcrumbs, pos=(0,0), span=(1,2), flag=wx.EXPAND|wx.LEFT, border=0)
        sizer.Add(self.docLabel, pos=(1,0), flag=wx.EXPAND|wx.LEFT, border=10)
        sizer.Add(self.doc, pos=(1,1), flag=wx.EXPAND|wx.BOTTOM, border=4)
        sizer.AddGrowableCol(1)
        sizer.AddGrowableRow(1)
        self.SetSizer(sizer)
        self.Bind(wx.EVT_TEXT, self.OnText)

    @contextmanager
    def ChangeEventsDisabled(self):
        '''Temporarily disable events that fire when the widget value changes'''
        orig_value = self._change_events_enabled
        self._change_events_enabled = False
        yield
        self._change_events_enabled = orig_value

    def OnText(self, event):
        '''Save widget contents back to the robot object'''
        if self._change_events_enabled:
            text = self.doc.GetValue()
            # N.B. popuplate appends rather than sets so we have to
            # make sure and call reset() first. 
            self.obj.doc.reset()
            self.obj.doc.populate(text.replace("\n", r'\n'))

    def GetValue(self):
        return self.doc.GetValue()

    def SetObject(self, obj):
        with self.ChangeEventsDisabled():
            self.obj = obj
            self.breadcrumbs.SetCrumbs(get_longname(obj))
            self.doc.Clear()
            self.doc.AppendText(obj.doc.value.replace(r'\n',"\n"))

class DocEditor(wx.stc.StyledTextCtrl):
    def __init__(self, *args, **kwargs):
        super(DocEditor, self).__init__(*args, **kwargs)
        self.desc = wx.stc.StyledTextCtrl(self, wx.ID_ANY)
        self.desclabel = wx.StaticText(self, wx.ID_ANY, "Description:")
        
        self.SetWrapMode(wx.stc.STC_WRAP_WORD)
        self.SetMarginLeft(10)
        self.SetMarginWidth(0,0)
        self.SetMarginWidth(1,0)
        self.StyleSetSpec(wx.stc.STC_STYLE_DEFAULT, "back:#efefef,fore:#e22222,italic")
        self.StyleClearAll()

class TableEditor(firebrick.Editor):
    def __init__(self, parent, obj):
        super(TableEditor, self).__init__(parent,obj)
        self.title = obj.name
        self.md5 = ""
        self._change_events_enabled = True
#        self.awesomebox = AwesomeBox(self, wx.ID_ANY)
        
        font = self.GetFont()
        dc = wx.WindowDC(self)
        dc.SetFont(font)
        width, height = dc.GetTextExtent("0")

        self.splitter = wx.SplitterWindow(self, wx.ID_ANY, style=wx.SP_LIVE_UPDATE)
        self.splitter.SetMinimumPaneSize(height+1)
        
        self.dtc = DynamicTableCtrl(self.splitter)

        self.doc = Header(self.splitter, editor=self)

        # it might be good to have a user preference for the default height
        # of the edit window. <shrug>
        self.splitter.SplitHorizontally(self.doc, self.dtc, (height*4)+2)
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.splitter, 1, wx.EXPAND)
#        sizer.Add(self.awesomebox, 0, wx.EXPAND)
        self.SetSizerAndFit(sizer)

        self.dtc.StyleSetSpec(STYLE_VARIABLE, "fore:#228b22")
        self.dtc.StyleSetSpec(STYLE_KEYWORD, "fore:#0000ff")
        self.dtc.StyleSetSpec(STYLE_SETTING, "bold")
        self.dtc.IndicatorSetStyle(0, wx.stc.STC_INDIC_SQUIGGLE)
        self.dtc.IndicatorSetForeground(0, wx.RED)

        self.dtc.Bind(wx.stc.EVT_STC_CHANGE, self.OnChange)
        self.dtc.Bind(wx.stc.EVT_STC_STYLENEEDED, self.OnStyleNeeded)
        self.Load(obj)
#        self.awesomebox.SetTCUK(obj)

    # *hack* *cough* *cough*
    # I need to find a better way to handle changes...
    @contextmanager
    def ChangeEventsDisabled(self):
        '''Temporarily disable change events'''
        orig_value = self._change_events_enabled
        self._change_events_enabled = False
        with self.dtc.AutoSaveDisabled():
            yield
        self._change_events_enabled = orig_value

    def GetChecksum(self):
        '''Return checksum of all data managed by the editor'''
        # get a checksum of the text plus the documentation, used to
        # determine whether the data has changed since the previous
        # call to this function.
        rows = self.dtc.GetRows()
        doc = self.doc.GetValue()
        m = hashlib.md5()
        m.update(str((rows,doc)))
        return m.digest()

    def _save_to_model(self, force_save = False):
        '''save, as in save from the control to the model, not save to disk'''

        with self.ChangeEventsDisabled():
            if not self.dtc.GetModify() and not force_save:
                # nothing changed. Our work here is done.
                return

            settings = dict([(key, item) 
                             for (key, item) 
                             in self.robj.__dict__.items() 
                             if self.robj.is_setting(key)])
            rows = self.dtc.GetRows()
            new_settings = {}
            for row in rows:
                cell0 = row[0].strip().lower()
                if re.match(r'^\[.*\]$', cell0):
                    new_settings[cell0[1:-1]] = row[1:]
            for (name, setting) in settings.items():
                if name in new_settings:
                    # WTF? popuplate appends rather than sets so
                    # I have to make sure and call reset()
                    setting.reset()
                    comment = None
                    if new_settings[name][-1].strip().startswith("#"):
                        comment = new_settings[name].pop().lstrip("# ")
                    setting.populate(new_settings[name])
                    setting.comment = comment
#                    self._set_setting(setting, new_setting[name])
                else:
                    setting.reset()
                    
#            self._set_setting("Documentation", [self._doc.GetEscapedValue()])
            steps = []
            for row in rows:
                # this regex matches a setting like [tags]...
                if not re.match(r"^\s*\[\s*(.*?)\s*\]\s*$", row[0]):
                    hash_comment = re.match("\s*#\s*(.*)$", row[0])
                    if hash_comment:
                        # must be converted to a robot Comment keyword
                        steps.append(Step(["Comment", hash_comment.group(1)] + row[1:]))
                    else:
                        if len(row) == 1 and len(row[0].strip()) == 0:
                            steps.append(Step(["${EMPTY}"]))
                        else:
                            steps.append(Step(row))

            if hasattr(self.robj, "steps"):
                self.robj.steps = steps
#            self._set_dirty()
#            self.dtc.SetSavePoint()

    def NeedsSave(self):
        '''Return True if the contents can be saved'''
        md5 = self.GetChecksum()
        return md5 != self.md5

    def OnChange(self, event):
        '''When the widget changes, save to the data model

        wxPython calls this waaaaaaay too often, so use
        the RunSoon method to cut down on how often we
        actually save to the model. Saving to the model
        is relatively cheap but it ain't free.
        '''
        if self._change_events_enabled:
            wx.GetApp().RunSoon(self._save_to_model)
        event.Skip()

    def OnStyleNeeded(self, event):
        '''Called in response to a EVT_STC_STYLENEEDED event
        
        This method is ultimately responsible for updating
        the attributes of each line. 
        '''
        # Danger Will Robinson!
        # there is still an inefficiency somewhere. For example,
        # if I click on the last cell of the last row and press
        # a single key (eg: 'x') this will get called twice. Is
        # that a bug in wxPython or my code somewhere?
        start = self.dtc.GetEndStyled()
        start_line = self.dtc.LineFromPosition(start)

        for line in range(start_line,self.dtc.GetLineCount()):
            self._style_line(line)

        # this resets the "style needed" position to the end,
        # signifying "no more styling is needed".
        self.dtc.StartStyling(self.dtc.GetLength(), 0x1f)
        self.dtc.SetStyling(1, wx.stc.STC_STYLE_DEFAULT)
        event.Skip()

    def _style_line(self, line):
        '''Style a single line, one column at a time'''
        linestart = self.dtc.PositionFromLine(line)
        lineend = max(self.dtc.GetLineEndPosition(line), linestart)

        cells = self.dtc.GetCells(line, indices=True)
        is_comment = False
        for column, (start, end)  in enumerate(cells):
            data = self.dtc.GetTextRange(start, end).strip().lower()
            self.dtc.StartStyling(start, 0x1f)
            is_comment = (is_comment or 
                          data.startswith("#") or 
                          (column == 0 and re.match("comment(\s|$)", data)))
            if is_comment:
                self.dtc.SetStyling(end-start, STYLE_COMMENT)
            else:
                self._style_cell(line, column, start, end, data)

    def _style_cell(self, row, column, start, end, data=None):
        '''Apply styling to an individual cell'''

        contents = data or self.dtc.GetTextRange(start, end).strip().lower()
        self.dtc.SetStyling(end-start, wx.stc.STC_STYLE_DEFAULT)
        if self._is_keyword(contents):
            self.dtc.StartStyling(start, 0x1f)
            self.dtc.SetStyling(end-start, STYLE_KEYWORD)
        elif column == 0 and re.match("\[.*?\]", data):
            self.dtc.StartStyling(start, 0x1f)
            self.dtc.SetStyling(end-start, STYLE_SETTING)

        # do variables last, so we catch any that are embedded in 
        # the cell
        self.dtc.AddStyleRegexp(start, end,  STYLE_VARIABLE, r'\${.*?}=?')

    def _is_keyword(self, string):
        if string == "log":
            return True


class TestCaseFileEditor(TableEditor):
    _id = "rwb.core.testcase_file_editor"

    def Load(self, obj):
        self.tcuk = obj
        wx.BeginBusyCursor()
        with self.ChangeEventsDisabled():
            self.dtc.Clear()
            settings = obj.setting_table
            need_separator = False
            for attr in (settings.suite_setup, settings.suite_teardown, 
                         settings.test_setup, settings.test_teardown,
                         settings.force_tags, settings.default_tags, 
                         settings.test_timeout, settings.test_template):
                if attr.is_set():
                    row = attr.as_list()
                    row[0] = "[%s]" % row[0]
                    self.dtc.AppendRow(row)
                    attrs = True
                    need_separator = True

            for meta in settings.metadata:
                if need_separator:
                    # blank line for visual separation
                    self.dtc.AppendRow([])
                    need_separator = False
                row = meta.as_list()
                row[0] = "[%s]" % row[0]
                self.dtc.AppendRow(row)
                metas = True

            if settings.metadata:
                need_separator = True

            for imp in obj.imports:
                if need_separator:
                    # blank line for visual separation
                    self.dtc.AppendRow([])
                    need_separator = False
                row = imp.as_list()
                row[0] = "[%s]" % row[0]
                self.dtc.AppendRow(row)

            if settings.metadata:
                need_separator = True

            for var in obj.variable_table.variables:
                if need_separator:
                    # blank line for visual separation
                    self.dtc.AppendRow([])
                    need_separator = False
                self.dtc.AppendRow(["[Variable]"] + var.as_list())
        wx.EndBusyCursor()

            
class TestDataDirectoryEditor(TableEditor):
    _id = "rwb.core.test_data_directory_editor"

    def Load(self, obj):
        with self.ChangeEventsDisabled():
            self.dtc.Clear()
            settings = obj.setting_table
            need_separator = False
            for attr in (settings.suite_setup, settings.suite_teardown, 
                         settings.test_setup, settings.test_teardown,
                         settings.force_tags):
                if attr.is_set():
                    row = attr.as_list()
                    row[0] = "[%s]" % row[0]
                    self.dtc.AppendRow(row)
                    attrs = True
                    need_separator = True

            for meta in settings.metadata:
                if need_separator:
                    # blank line for visual separation
                    self.dtc.AppendRow([])
                    need_separator = False
                row = meta.as_list()
                row[0] = "[%s]" % row[0]
                self.dtc.AppendRow(row)
                metas = True

            if settings.metadata:
                need_separator = True

            for imp in obj.imports:
                if need_separator:
                    # blank line for visual separation
                    self.dtc.AppendRow([])
                    need_separator = False
                row = imp.as_list()
                row[0] = "[%s]" % row[0]
                self.dtc.AppendRow(row)

            if settings.metadata:
                need_separator = True

            for var in obj.variable_table.variables:
                if need_separator:
                    # blank line for visual separation
                    self.dtc.AppendRow([])
                    need_separator = False
                self.dtc.AppendRow(["[Variable]"] + var.as_list())
            
class TestCaseEditor(TableEditor):
    def GetRobotSuite(self):
        '''Return the suite associated with this editor'''
        return self._get_suite()

    def IsEditable(self):
        # probably should also check that file is writable?
        return self.tcuk is not None

    def _get_suite(self):
        suite = self.robj.parent
        while suite and not isinstance(suite, TestCaseFile):
            suite = suite.parent
        return suite

    def Save(self):
        # need to find containing test suite
        suite = self._get_suite()
        fmt = TsvFormatter(suite)
        print fmt

    def HighlightTarget(self, step_number):
        '''Target is assumed to be a number representing a step'''
        # N.B. RSA counts lines starting at one, DTC counts lines
        # starting at 0. 
        line = self._find_first_step_offset() + (step_number-1)
        start = self.dtc.PositionFromLine(line)
        end = self.dtc.GetLineEndPosition(line)
        self.dtc.SetSelection(start, end)
        

    def _find_first_step_offset(self):
        '''return row number of first step

        This returns the row number of the first row that doesn't look
        like it has a setting on it.  This method assumes all settings
        come before all steps (not the best of assumptions, but that
        should generally be true)
        '''
        self.dtc.GetRows()
        for i, row in enumerate(self.dtc.GetRows()):
            if not re.match(r'^\[.*\]', row[0]):
                break
        return i

    def Load(self, obj):
        self.robj = obj
        wx.BeginBusyCursor()
        with self.ChangeEventsDisabled():
            self.doc.SetObject(obj)
            self.dtc.Clear()
            for setting_name in ("tags","setup","teardown","template","timeout"):
                attr = getattr(obj, setting_name, None)
                if attr is not None and attr.is_set():
                    self.dtc.AppendRow(attr.as_list())

            for step in obj.steps:
                if step.is_for_loop():
                    # argh! Stupid parser treats for loops specially
                    self.dtc.AppendRow(step.as_list())
                    for loop_step in step.steps:
                        self.dtc.AppendRow([""] + loop_step.as_list())
                else:
                    row = step.as_list()
                    if row[0].lower() == "comment":
                        if len(row) > 1:
                            row = row[1:]
                        else:
                            row = [""]
                        row[0] = "# %s" % row[0]
                    self.dtc.AppendRow(row)
        self.dtc.SetSavePoint()
        self.md5 = self.GetChecksum()
        wx.EndBusyCursor()
#            for step in obj.steps:
#                self.dtc.AppendRow(step.as_list())

class TextEditor(firebrick.Editor):
    _id = "rwb.core.TextEditor"

    def __init__(self, parent, obj):
        super(TextEditor, self).__init__(parent, obj)
        self.title = obj.name
        self.text = CodeEditor(self)
#        self.text = wx.stc.StyledTextCtrl(self)
        self.text.SetScrollWidth(1000)
        basename, extension = os.path.splitext(obj.path)
        if extension == ".py":
            self._set_mode("python")
        elif extension == ".xml":
            self._set_mode("mockdata")
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.text, 1, wx.EXPAND)
        self.SetSizerAndFit(sizer)
        self.Load(obj)

    def _set_mode(self, mode="python"):
        if mode == "python":
            print "using PythonMode (1)"
            PythonMode().apply(self.text)
        else:
            print "using PythonMode (2)"
            # obviously this isn't the right default, but
            # it's all I got..
            PythonMode().apply(self.text)

        # NOT a permanent solution...
        faces = { 'times': 'Times',
                  'mono' : 'Courier',
                  'helv' : 'Helvetica',
                  'other': 'new century schoolbook',
                  'size' : 9,
                  'size2': 8,
                  }

        self.text.Bind(wx.stc.EVT_STC_MARGINCLICK, self.OnMarginClick)

        self.text.StyleSetSpec(wx.stc.STC_STYLE_DEFAULT,     
                               "face:%(helv)s,size:%(size)d" % faces)
        self.text.StyleClearAll()  # Reset all to be like the default

        # Global default styles for all languages
        self.text.StyleSetSpec(wx.stc.STC_STYLE_DEFAULT,     
                               "face:%(helv)s,size:%(size)d" % faces)
        self.text.StyleSetSpec(wx.stc.STC_STYLE_LINENUMBER,  
                               "back:#C0C0C0,face:%(helv)s,size:%(size2)d" % faces)
        self.text.StyleSetSpec(wx.stc.STC_STYLE_CONTROLCHAR, 
                               "face:%(other)s" % faces)
        self.text.StyleSetSpec(wx.stc.STC_STYLE_BRACELIGHT,  
                               "fore:#FFFFFF,back:#0000FF,bold")
        self.text.StyleSetSpec(wx.stc.STC_STYLE_BRACEBAD,    
                               "fore:#000000,back:#FF0000,bold")

        # Python styles
        self.text.StyleSetSpec(wx.stc.STC_P_DEFAULT, "fore:#000000,face:%(helv)s,size:%(size)d" % faces)
        self.text.StyleSetSpec(wx.stc.STC_P_COMMENTLINE, "fore:#e22222,face:%(other)s,size:%(size)d" % faces)
        self.text.StyleSetSpec(wx.stc.STC_P_NUMBER, "fore:#007F7F,size:%(size)d" % faces)
        self.text.StyleSetSpec(wx.stc.STC_P_STRING, "fore:#7F007F,face:%(helv)s,size:%(size)d" % faces)
        self.text.StyleSetSpec(wx.stc.STC_P_CHARACTER, "fore:#7F007F,face:%(helv)s,size:%(size)d" % faces)
        self.text.StyleSetSpec(wx.stc.STC_P_WORD, "fore:#00007F,bold,size:%(size)d" % faces)
        self.text.StyleSetSpec(wx.stc.STC_P_TRIPLE, "fore:#7F0000,size:%(size)d" % faces)
        self.text.StyleSetSpec(wx.stc.STC_P_TRIPLEDOUBLE, "fore:#7F0000,size:%(size)d" % faces)
        self.text.StyleSetSpec(wx.stc.STC_P_CLASSNAME, "fore:#0000FF,bold,underline,size:%(size)d" % faces)
        self.text.StyleSetSpec(wx.stc.STC_P_DEFNAME, "fore:#007F7F,bold,size:%(size)d" % faces)
        self.text.StyleSetSpec(wx.stc.STC_P_OPERATOR, "bold,size:%(size)d" % faces)
        self.text.StyleSetSpec(wx.stc.STC_P_IDENTIFIER, "fore:#000000,face:%(helv)s,size:%(size)d" % faces)
        self.text.StyleSetSpec(wx.stc.STC_P_COMMENTBLOCK, "fore:#7F7F7F,size:%(size)d" % faces)
        self.text.StyleSetSpec(wx.stc.STC_P_STRINGEOL, "fore:#000000,face:%(mono)s,back:#E0C0E0,eol,size:%(size)d" % faces)


    def OnMarginClick(self, evt):
        # fold and unfold as needed
        if evt.GetMargin() == 2:
            if evt.GetShift() and evt.GetControl():
                self.FoldAll()
            else:
                lineClicked = self.text.LineFromPosition(evt.GetPosition())

                if self.text.GetFoldLevel(lineClicked) & wx.stc.STC_FOLDLEVELHEADERFLAG:
                    if evt.GetShift():
                        self.text.SetFoldExpanded(lineClicked, True)
                        self.Expand(lineClicked, True, True, 1)
                    elif evt.GetControl():
                        if self.text.GetFoldExpanded(lineClicked):
                            self.text.SetFoldExpanded(lineClicked, False)
                            self.Expand(lineClicked, False, True, 0)
                        else:
                            self.text.SetFoldExpanded(lineClicked, True)
                            self.Expand(lineClicked, True, True, 100)
                    else:
                        self.text.ToggleFold(lineClicked)


    def FoldAll(self):
        lineCount = self.text.GetLineCount()
        expanding = True

        # find out if we are folding or unfolding
        for lineNum in range(lineCount):
            if self.text.GetFoldLevel(lineNum) & wx.stc.STC_FOLDLEVELHEADERFLAG:
                expanding = not self.text.GetFoldExpanded(lineNum)
                break

        lineNum = 0

        while lineNum < lineCount:
            level = self.text.GetFoldLevel(lineNum)
            if level & wx.stc.STC_FOLDLEVELHEADERFLAG and \
               (level & wx.stc.STC_FOLDLEVELNUMBERMASK) == wx.stc.STC_FOLDLEVELBASE:

                if expanding:
                    self.text.SetFoldExpanded(lineNum, True)
                    lineNum = self.Expand(lineNum, True)
                    lineNum = lineNum - 1
                else:
                    lastChild = self.text.GetLastChild(lineNum, -1)
                    self.text.SetFoldExpanded(lineNum, False)

                    if lastChild > lineNum:
                        self.text.HideLines(lineNum+1, lastChild)

            lineNum = lineNum + 1



    def Expand(self, line, doExpand, force=False, visLevels=0, level=-1):
        lastChild = self.text.GetLastChild(line, level)
        line = line + 1

        while line <= lastChild:
            if force:
                if visLevels > 0:
                    self.text.ShowLines(line, line)
                else:
                    self.text.HideLines(line, line)
            else:
                if doExpand:
                    self.text.ShowLines(line, line)

            if level == -1:
                level = self.text.GetFoldLevel(line)

            if level & wx.stc.STC_FOLDLEVELHEADERFLAG:
                if force:
                    if visLevels > 1:
                        self.text.SetFoldExpanded(line, True)
                    else:
                        self.text.SetFoldExpanded(line, False)

                    line = self.Expand(line, doExpand, force, visLevels-1)

                else:
                    if doExpand and self.text.GetFoldExpanded(line):
                        line = self.Expand(line, True, force, visLevels-1)
                    else:
                        line = self.Expand(line, False, force, visLevels-1)
            else:
                line = line + 1

        return line


    def Load(self, obj):
        self.text.Clear()
        f = open(obj.path, "r")
        data = f.read()
        f.close()
        self.text.AppendText(data)
#        self.text.AppendText("pretend %s was loaded\n" % str(obj))

